﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;

namespace ComicsEditor
{
    public class Downloader
    {
        public static Downloader Default;

        private class DownloadJob
        {
            public string Url;
            public byte[] Hash;
            public string FileName;
            public bool Complete = false;
            public int Retries = 3;

            public DownloadJob(string Url, byte[] Hash, string FileName)
            {
                this.Url = Url;
                this.Hash = Hash;
                this.FileName = FileName;
            }
        }
        private Dictionary<string, DownloadJob> Jobs = new Dictionary<string, DownloadJob>();
        private Queue<DownloadJob> queue = new Queue<DownloadJob>();

        private Thread threadDownloader = null;
        private bool signalThreadQuit = false;
        private AutoResetEvent queueUpdate = new AutoResetEvent(false);

        public Downloader()
        {
            threadDownloader = new Thread(DownloadMain);
            threadDownloader.IsBackground = true;
            threadDownloader.Start();
        }

        public void Download(string Url, byte[] Hash)
        {
            string key = ComicInfo.HashToString(Hash);
            DownloadJob job;
            if (Jobs.ContainsKey(key))
            {
                job = Jobs[key];
                if (job.Url != Url)
                {
                    lock (queue)
                    {
                        job.Url = Url;
                        if (job.Complete || job.Retries == 0)
                        {
                            queue.Enqueue(job);
                            queueUpdate.Set();
                        }
                        job.Complete = false;
                        job.Retries = 3;
                    }
                }
            }
            else
            {
                if (!Directory.Exists(ComicInfo.CachePath))
                    Directory.CreateDirectory(ComicInfo.CachePath);
                job = new DownloadJob(Url, Hash, ComicInfo.CachePath + Path.DirectorySeparatorChar + key);
                Jobs.Add(key, job);
                if (File.Exists(job.FileName))
                    using (FileStream fs = File.Open(job.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        job.Complete = CheckStream(fs, Hash);
                if (!job.Complete)
                {
                    lock (queue)
                        queue.Enqueue(job);
                    queueUpdate.Set();
                }
            }
        }

        public string GetFile(byte[] Hash)
        {
            return Jobs[ComicInfo.HashToString(Hash)].FileName;
        }

        public bool IsCompleted(byte[] Hash)
        {
            return Jobs[ComicInfo.HashToString(Hash)].Complete;
        }

        public bool IsFailed(byte[] Hash)
        {
            return Jobs[ComicInfo.HashToString(Hash)].Retries == 0;
        }

        public byte[] Download(string Url)
        {
            try
            {
                string tempName = ComicInfo.CachePath + Path.DirectorySeparatorChar + "temp";
                byte[] hash;
                using (FileStream fs = File.Open(tempName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
                {
                    DownloadToStream(Url, fs);
                    fs.Position = 0;
                    System.Security.Cryptography.HashAlgorithm sha256 = new System.Security.Cryptography.SHA256Managed();
                    hash = sha256.ComputeHash(fs);
                }
                string fileName = ComicInfo.CachePath + Path.DirectorySeparatorChar + ComicInfo.HashToString(hash);
                if (File.Exists(fileName))
                    File.Delete(tempName);
                else
                    File.Move(tempName, fileName);
                return hash;
            }
            catch
            {
                return null;
            }
        }

        private bool CheckStream(Stream stream, byte[] Hash)
        {
            System.Security.Cryptography.HashAlgorithm sha256 = new System.Security.Cryptography.SHA256Managed();
            byte[] hash = sha256.ComputeHash(stream);
            int i;
            for (i = 0; i < hash.Length && Hash[i] == hash[i]; i++);
            return i >= hash.Length;
        }

        private void DownloadMain()
        {
            while (!signalThreadQuit)
            {
                while (true)
                {
                    DownloadJob job = null;
                    lock (queue)
                        if (queue.Count > 0)
                            job = queue.Dequeue();
                    if (job == null)
                        break;
                    FileStream fs = File.Open(job.FileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                    try
                    {
                        DownloadToStream(job.Url, fs);
                        fs.Position = 0;
                        if (!CheckStream(fs, job.Hash))
                            throw new Exception();

                        job.Complete = true;
                    }
                    catch
                    {
                        lock (queue)
                        {
                            job.Retries--;
                            if (job.Retries > 0)
                            {
                                queue.Enqueue(job);
                                queueUpdate.Set();
                            }
                        }
                    }
                    finally
                    {
                        fs.Close();
                    }
                }
                queueUpdate.WaitOne();
            }
        }

        private void DownloadToStream(string Url, Stream targetStream)
        {
            WebRequest request = HttpWebRequest.Create(Url);
            request.Proxy = WebRequest.DefaultWebProxy;

            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();

            byte[] buffer = new byte[65536];
            int read; ;
            while ((read = stream.Read(buffer, 0, 65536)) != 0)
                targetStream.Write(buffer, 0, read);
            stream.Close();
            response.Close();
        }
    }
}
